/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { RuleListUtil } from "../../utils/common/DefectsList";
import { ArkFile, ts } from "arkanalyzer/lib";
import { AdviceChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { AstMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";

interface Signature {
  name: string;
  isStatic: boolean;
}

export class AdjacentOverloadSignaturesCheck implements AdviceChecker {
  readonly metaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/adjacent-overload-signatures.md',
    description: 'All ${methodName} signatures should be adjacent.',
  };
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];

  private containerMatcher: AstMatcher = {
    matcherType: MatcherTypes.AST,
    match: (node: ts.Node) => ts.isSourceFile(node) || ts.isModuleDeclaration(node) ||
      ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node) || ts.isTypeLiteralNode(node)
  };

  public registerMatchers(): MatcherCallback[] {
    return [{ matcher: this.containerMatcher, callback: this.check }];
  }

  public beforeCheck(): void {}

  public afterCheck(): void {}

  public check = (node: ts.Node, file: ArkFile): void => {
    const members = this.getMembers(node);
    if (!members) {
      return;
    }
    const seen = new Map<string, ts.Node>();
    let last: Signature | null = null;

    for (const member of members) {
      const sig = this.extractSignature(member);
      if (!sig) {
        last = null;
        continue;
      }

      const key = `${sig.name}:${sig.isStatic}`;
      const prev = seen.get(key);

      if (prev && (!last || last.name !== sig.name || last.isStatic !== sig.isStatic)) {
        this.addIssue(member, node.getSourceFile(), file, sig.name);
      }

      if (!prev) {
        seen.set(key, member);
      }
      last = sig;
    }
  };

  private getMembers(node: ts.Node): ts.NodeArray<ts.Node> | undefined {
    if (ts.isSourceFile(node)) {
      return node.statements as ts.NodeArray<ts.Node>;
    }
    if (ts.isModuleDeclaration(node) && node.body && ts.isModuleBlock(node.body)) {
      return node.body.statements as ts.NodeArray<ts.Node>;
    }
    if (ts.isInterfaceDeclaration(node)) {
      return node.members as ts.NodeArray<ts.Node>;
    }
    if (ts.isClassDeclaration(node)) {
      return node.members as ts.NodeArray<ts.Node>;
    }
    if (ts.isTypeLiteralNode(node)) {
      return node.members as ts.NodeArray<ts.Node>;
    }
    return undefined;
  }

  private extractSignature(member: ts.Node): Signature | null {
    if (ts.isCallSignatureDeclaration(member)) {
      return { name: 'call', isStatic: false };
    }
    if (ts.isConstructorDeclaration(member)) {
      return { name: 'constructor', isStatic: false };
    }

    if (ts.isMethodSignature(member) || ts.isMethodDeclaration(member)) {
      if (member.modifiers?.some(m => m.kind === ts.SyntaxKind.AbstractKeyword)) {
        return null;
      }
      const name = this.getName(member.name);
      if (!name) {
        return null;
      }
      const isStatic = member.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
      return { name, isStatic };
    }

    if (ts.isFunctionDeclaration(member)) {
      const name = member.name?.text;
      return name ? { name, isStatic: false } : null;
    }

    return null;
  }

  private getName(name: ts.PropertyName | undefined): string | null {
    if (!name) {
      return null;
    }
    if (ts.isComputedPropertyName(name)) {
      const expr = name.expression;
      return (ts.isStringLiteral(expr) || ts.isIdentifier(expr)) ? expr.text : null;
    }
    if (ts.isIdentifier(name)) {
      return name.text;
    }
    if (ts.isPrivateIdentifier(name)) {
      return `#${name.text}`;
    }
    return null;
  }

  private addIssue(member: ts.Node, sourceFile: ts.SourceFile, file: ArkFile, name: string): void {
    const start = sourceFile.getLineAndCharacterOfPosition(member.getStart());
    const end = sourceFile.getLineAndCharacterOfPosition(member.getEnd());

    const defect = new Defects(
      start.line + 1,
      start.character + 1,
      end.character + 1,
      `All ${name} signatures should be adjacent.`,
      this.rule.alert ?? this.metaData.severity,
      this.rule.ruleId,
      file.getFilePath(),
      this.metaData.ruleDocPath,
      true,
      false,
      false
    );

    this.defects.push(defect);
    const issue = new IssueReport(defect, undefined);
    RuleListUtil.push(defect);
    this.issues.push(issue);
  }
}
